Chapter 6 Data Visualizations
6.1 Room Temperature Reduction
6.1.1 Task
As part of an energy optimization, you lower the room temperatures in a room and would now like to show the reduction effect using the time series of the room temperature sensor. In the example below you make two optimizations at different dates.
You want to create a time series plot with
the daily median, min and max value
the overall median of each period
the desired setpoint
6.1.2 Basis
- Time series data from e.g. a temperature sensor with unaligned time intervals
6.1.3 Solution
library(dplyr)
library(lubridate)
library(dygraphs)
library(xts)
library(redutils)
library(RColorBrewer)
# Settings
tempSetpoint = 22.0
startDate = "2018-11-01"
endDate = "2019-02-01"
optiDate1 = "2018-12-17"
optiLabel1 = "Optimization I"
optiDate2 = "2019-01-03"
optiLabel2 = "Optimization II"
optiDelayDays = 5
# read and print data
df <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/flatTempHum.csv",
stringsAsFactors=FALSE,
sep =";")
# select temperature and remove empty cells
df <- df %>% select(time, FlatA_Temp) %>% na.omit()
# create column with day for later grouping
df$time <- parse_date_time(df$time, "YmdHMS", tz = "Europe/Zurich")
df$day <- as.Date(cut(df$time, breaks = "day"))
df$day <- as.Date(as.character(df$day,"%Y-%m-%d"))
# filter time range
df <- df %>% filter(day > startDate, day < endDate)
# calculate daily median, min and max of temperature
df <- df %>%
group_by(day) %>%
mutate(minDay = min(as.numeric(FlatA_Temp)),
medianDay = median(as.numeric(FlatA_Temp)),
maxDay = max(as.numeric(FlatA_Temp))
) %>%
ungroup()
# shrink down to daily values and remove rows with empty values
df <- df %>% select(day, medianDay, minDay, maxDay) %>% unique() %>% na.omit()
# calculate medians for time ranges
df <- df %>%
mutate(period = ifelse(day >= startDate & day <= optiDate1,
"Baseline",
ifelse((day >= (as.Date(optiDate1) + optiDelayDays))
& (day <= optiDate2),
"Opti1",
ifelse((day >= (as.Date(optiDate2) + optiDelayDays))
& (day <= endDate),
"Opti2",
NA)
)))
df <- df %>%
group_by(period) %>%
mutate(medianPeriod = ifelse(is.na(period), NA, median(medianDay))) %>%
ungroup() %>%
select(-period)
# create xts object for plotting
plotdata <- xts( x=df[,-1], order.by=df$day)
# plot graph
dygraph(plotdata, main = "Room Temperature Reduction") %>%
dyAxis("x", drawGrid = FALSE) %>%
dySeries(c("minDay", "medianDay", "maxDay"),
label = "Temperature") %>%
dySeries(c("medianPeriod"),
label = "Median Period",
strokePattern = "dashed") %>%
dyOptions(colors = RColorBrewer::brewer.pal(3, "Set2")) %>%
dyEvent(x = optiDate1,
label = optiLabel1,
labelLoc = "bottom",
color = "slategray",
strokePattern = "dotted") %>%
dyEvent(x = optiDate2,
label = optiLabel2,
labelLoc = "bottom",
color = "slategray",
strokePattern = "dotted") %>%
dyLimit(tempSetpoint,
color = "red",
label = "Target Setpoint") %>%
dyRangeSelector() %>%
dyLegend(show = "always")6.1.4 Discussion
In this example we used the dygraph package to create the graph. This package is fast and allows to show a rangeslider on the bottom of the graph. The exact same graph but without a slider is as well possible with ggplot.
Please note that the calculation of the periodic median after optimization I and II starts delayed because it takes time until the building has cooled down.
6.2 Energy Consumption Before/After per Month
6.2.1 Task
6.2.2 Basis
6.2.3 Solution
library(dplyr)
library(lubridate)
library(ggplot2)
library(plotly)
library(viridis)
library(ggthemes)
# load csv file
df <- read.csv2("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/flatHeatAndHotWater.csv",
stringsAsFactors=FALSE)
# filter flat
df <- df %>% select(timestamp, Adr02_energyHeat)
colnames(df) <- c("timestamp", "meterValue")
# calculate consumption value per month
# pay attention, the value of 2010-02-01 00:00:00 represents the meter reading on february first,
# so the consumption for february first is value(march) - value(february)!
df <- df %>% mutate(value = lead(meterValue) - meterValue)
# remove counter value column
df <- df %>% select(-meterValue)
# add metadata for later filtering
df$timestamp <- parse_date_time(df$timestamp, "YmdHMS", tz = "Europe/Zurich")
df$year <- year(df$timestamp)
df$month <- month(df$timestamp)
# date to differentiate before/after
dateOptimization <- "2017-09-01"
# create two separate data frames for later visualization (before is grey, after is colored)
df.after<- df %>% filter(timestamp >= dateOptimization) %>% na.omit()
df.before <- df %>% filter(timestamp <= dateOptimization) %>% na.omit()
# add statistical band values and median of phase "before"
df.before <- df.before %>% group_by(month) %>% dplyr::mutate(median = quantile(value, 0.5),
qLower = quantile(value, 0.05),
qUpper = quantile(value, 0.95)
) %>% ungroup()
# calculate values to later generate different colors
count.all <- nrow(df %>% select(year) %>% unique())
count.after <- nrow(df.after %>% select(year) %>% unique())
count.baseline <- nrow(df.before %>% select(year) %>% unique())
# correction in case the date is during a year
if((count.after+count.baseline) > count.all){count.baseline <- count.baseline - 1}
# plot graph with all time series
g <- ggplot() +
geom_line(data = df.before, aes(x = month, y = value, group = year, color = factor(year)), alpha = 0.5) +
geom_ribbon(data = df.before, aes(x = month, ymin = qLower, ymax = qUpper), fill = "orange", alpha = 0.3) +
geom_line(data = df.before, aes(x = month, y = median), colour = "orange", linetype = "dashed") +
geom_line(data = df.after, aes(x = month, y = value, group = year, colour = factor(year)), alpha = 0.8) +
geom_point(data = df.after, aes(x = month, y = value, group = year, color = factor(year)), alpha = 0.8, shape = 21, fill = "white") +
theme_economist() +
labs(title = "Before/After Optimization\n", x = "Month", y = "Energy Consumption \n(kWh/month)", color = "Years\n") +
scale_x_discrete(limits = month.abb) +
scale_color_manual(values=c(rep("grey", count.baseline), viridis::viridis(count.after)))
ggplotly(g)6.2.4 Discussion
6.3 Building Energy Signature
6.3.1 Task
You want to create a scatter plot with
the daily mean outside temperature on the x-axis
the daily energy consumption on the y-axis
points colored according to season
6.3.2 Basis
Two separate csv files with time series data from the outside temperature and the energy data with unaligned time intervals
Energy consumption time series from a energy meter with steadily increasing meter values
6.3.3 Solution
After reading in the two time series the data has to get aggregated per day and then merged. Note that during the aggregation of the energy data you have to calculate the daily conspumption from the steadiliy increasing meter values as well.
Create a new script, copy/paste the following code and run it:
library(ggplot2)
library(plotly)
library(dplyr)
library(redutils)
library(lubridate)
# load time series data and aggregate daily mean values
dfOutsideTemp <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/centralOutsideTemp.csv",
stringsAsFactors=FALSE,
sep =";")
dfOutsideTemp$time <- parse_date_time(dfOutsideTemp$time,
order = "YmdHMS",
tz = "Europe/Zurich")
dfOutsideTemp$day <- as.Date(cut(dfOutsideTemp$time, breaks = "day"))
dfOutsideTemp <- dfOutsideTemp %>%
group_by(day) %>%
mutate(tempMean = mean(centralOutsideTemp)) %>%
ungroup()
dfOutsideTemp <- dfOutsideTemp %>%
select(day, tempMean) %>%
unique() %>%
na.omit()
dfHeatEnergy <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/centralHeating.csv",
stringsAsFactors=FALSE,
sep =";")
dfHeatEnergy <- dfHeatEnergy %>%
select(time, energyHeatingMeter) %>%
na.omit()
dfHeatEnergy$time <- parse_date_time(dfHeatEnergy$time,
orders = "YmdHMS",
tz = "Europe/Zurich")
dfHeatEnergy$day <- as.Date(cut(dfHeatEnergy$time, breaks = "day"))
dfHeatEnergy <- dfHeatEnergy %>%
group_by(day) %>%
mutate(energyMax = max(energyHeatingMeter)) %>%
ungroup()
dfHeatEnergy <- dfHeatEnergy %>%
select(day, energyMax) %>%
unique() %>%
na.omit()
dfHeatEnergy <- dfHeatEnergy %>%
mutate(energyCons = energyMax - lag(energyMax)) %>%
select(-energyMax) %>%
na.omit()
# merge the data in a tidy format
df <- merge(dfOutsideTemp, dfHeatEnergy, by = "day")
# calculate season
df <- df %>% mutate(season = redutils::getSeason(df$day))
# static chart with ggplot
p <- ggplot2::ggplot(df) +
ggplot2::geom_point(aes(x = tempMean,
y = energyCons,
color = season,
alpha = 0.1,
text = paste("</br>Date: ", as.Date(df$day),
"</br>Temp: ", round(df$tempMean, digits = 1), "\u00B0C",
"</br>Energy: ", round(df$energyCons, digits = 0), "kWh/d",
"</br>Season: ", df$season))
) +
scale_color_manual(values=c("#440154", "#2db27d", "#fde725", "#365c8d")) +
ggtitle("Building Energy Signature") +
theme_minimal() +
theme(
legend.position="none",
plot.title = element_text(hjust = 0.5)
)
# interactive chart
plotly::ggplotly(p, tooltip = c("text")) %>%
layout(xaxis = list(title = "Outside temperature (\u00B0C)",
range = c(min(-5,min(df$tempMean)), max(35,max(df$tempMean))), zeroline = F),
yaxis = list(title = "Daily energy consumption (kWh/d)",
range = c(-5, max(df$energyCons) + 10)),
showlegend = TRUE
) %>%
plotly::config(displayModeBar = FALSE, displaylogo = FALSE)6.4 Daily Energy Profiles
# change language to English, otherwise weekdays are in local language
Sys.setlocale("LC_TIME", "English")## [1] "English_United States.1252"
library(plotly)
library(dplyr)
library(lubridate)
# load time series data
df <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/eboBookEleMeter.csv",
stringsAsFactors=FALSE,
sep =";")
# rename column names
colnames(df) <- c("timestamp", "meterValue")
df$timestamp <- parse_date_time(df$timestamp,
orders = "YmdHMS",
tz = "Europe/Zurich")
df$timestamp <- force_tz(df$timestamp, tzone = "UTC")
# uncomment to filter time range if necessary
#df <- df %>% filter(timestamp > "2015-03-01 00:00:00", timestamp < "2015-04-01 00:00:00")
# Fill missing values with NA
grid.df <- data.frame(timestamp = seq(min(df$timestamp, na.rm = TRUE),
max(df$timestamp, na.rm = TRUE),
by = "15 mins"))
df <- merge(df, grid.df, all = TRUE)
# convert steadily counting energy meter value from kWh to power in kW
df <- df %>%
mutate(value = (meterValue - lag(meterValue))*4) %>%
select(-meterValue) %>%
na.omit()
# remove negative values which occur beause of change summer/winter time
df <- df %>% filter(value >= 0)
# add metadata for later grouping and visualization purposes
df$x <- hour(df$timestamp) + minute(df$timestamp)/60 + second(df$timestamp) / 3600
df$weekday <- weekdays(df$timestamp)
df$weekday <- factor(df$weekday, c("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday", "Sunday"))
df$day <- as.Date(df$timestamp, format = "%Y-%m-%d %H:%M:%S")
df <- df %>% mutate(value = ifelse(x == 0.00, NA, df$value))
# plot graph with all time series
rangeX <- seq(0,24,0.25)
maxValue <- max(df$value, na.rm = TRUE)*1.05
df %>%
highlight_key(~day) %>%
plot_ly(x=~x,
y=~value,
color=~weekday,
type="scatter",
mode="lines",
line = list(width = 1),
alpha = 0.15,
colors = "dodgerblue4",
text = ~day,
hovertemplate = paste("Time: ", format(df$timestamp, "%H:%M"),
"<br>Date: ", format(df$timestamp, "%Y-%m-%d"),
"<br>Value: %{y:.0f}")) %>%
# workaround with add_trace to have fixed y axis when selecting a dedicated day
add_trace(x = 0, y = 0, type = "scatter", showlegend = FALSE, opacity=0) %>%
add_trace(x = 24, y = maxValue, type = "scatter", showlegend = FALSE, opacity=0) %>%
layout(title = "Superimposed Profiles of Power Consumption per 15 min",
showlegend = TRUE,
xaxis = list(
title = "Hour of day",
range = rangeX,
tickvals = list(0, 3, 6, 9, 12, 15, 18, 21),
showline=TRUE
),
yaxis = list(
title = "Power (kW)",
range = c(0, maxValue)
)
) %>%
highlight(on = "plotly_hover",
off = "plotly_doubleclick",
color = "orange",
opacityDim = 1.0,
selected = attrs_selected(showlegend = FALSE)) %>% # this hides elements in the legend
plotly::config(modeBarButtons = list(list("toImage")), displaylogo = FALSE)